#ifndef     __GPIO_H
#define     __GPIO_H

#include "./compat.h"

namespace device::gpio {


enum class Mode : uint8_t {
    PPOut = 0, // 推挽输出
    ODOut = 1, // 开漏输出
    AfPPOut = 2, // 复用推挽输出
    AfODOut = 3, // 复用开漏输出

    FloatInput = 4, // 输入模式
    Input = 5, // 上拉或者下拉输入
    AnalogInput = 6, // 模拟输入
};

enum class Pupd : uint8_t {
    Up = 0,
    Down = 1,
};

#define PORT(X)     reinterpret_cast<GPIO_TypeDef *>(X)

namespace mode {

template <Mode>
inline constexpr uint8_t value = 0; // 默认模拟输出

template <>
inline constexpr uint8_t value<Mode::PPOut> = 0x03;

template <>
inline constexpr uint8_t value<Mode::ODOut> = 0x07;

template <>
inline constexpr uint8_t value<Mode::AfPPOut> = 0x0B;

template <>
inline constexpr uint8_t value<Mode::AfODOut> = 0x0F;

template <>
inline constexpr uint8_t value<Mode::FloatInput> = 0x04;

template <>
inline constexpr uint8_t value<Mode::Input> = 0x08;

}

template <uint32_t Base, uint8_t Pin>
struct Gpio {

    template <Mode m>
    static inline void SetMode() {
        static_assert(Pin < 16, "pin must < 16");
        constexpr uint8_t val = mode::value<m>;

        if constexpr (Pin < 8) {
            constexpr uint32_t offset = 4 * Pin;
			constexpr uint32_t mask = 0x0F << offset;
            PORT(Base)->CRL = PORT(Base)->CRL & (~mask) | (val << offset);
        } else {
            constexpr uint32_t offset = 4 * (Pin - 8);
			constexpr uint32_t mask = 0x0F << offset;
            PORT(Base)->CRH = PORT(Base)->CRH & (~mask) | (val << offset);
        }
    }

    static inline void Set() {
        PORT(Base)->BSRR = 0x01 << Pin;
    }

    static inline void Clr() {
        PORT(Base)->BRR = 0x01 << Pin;
    }

    static inline bool Read() {
        return (PORT(Base)->IDR & (0x01 << Pin)) != 0;
    }

    template <Pupd pupd>
    static inline void SetPuPd() {
        static_assert(Pin < 16, "pin must < 16");
        if constexpr (pupd == Pupd::Up) {
            Set();
        } else {
            Clr();
        }
    }
};


template <Mode mode, typename... Ts>
inline void ConfigPins() {
    ((Ts::template SetMode<mode>()),...);
}

template <typename... Ts>
inline void ConfigPPOut() {
    ConfigPins<Mode::PPOut, Ts...>();
}

template <typename... Ts>
inline void ConfigODOut() {
    ConfigPins<Mode::ODOut, Ts...>();
}

template <typename... Ts>
inline void ConfigAfPPOut() {
    ConfigPins<Mode::AfPPOut, Ts...>();
}

template <typename... Ts>
inline void ConfigAfODOut() {
    ConfigPins<Mode::AfODOut, Ts...>();
}

template <typename... Ts>
inline void ConfigFloatInput() {
    ConfigPins<Mode::FloatInput, Ts...>();
}

template <typename... Ts>
inline void ConfigUpInput() {
    ConfigPins<Mode::Input, Ts...>();
    (Ts::Set(), ...);
}

template <typename... Ts>
inline void ConfigDownInput() {
    ConfigPins<Mode::Input, Ts...>();
    (Ts::Clr(), ...);
}

template <typename... Ts>
inline void ConfigAnalogInput() {
    ConfigPins<Mode::AnalogInput, Ts...>();
}

template <uint8_t Pin>
using PA = Gpio<GPIOA_BASE, Pin>;

template <uint8_t Pin>
using PB = Gpio<GPIOB_BASE, Pin>;

template <uint8_t Pin>
using PC = Gpio<GPIOC_BASE, Pin>;

template <uint8_t Pin>
using PD = Gpio<GPIOD_BASE, Pin>;

template <uint8_t Pin>
using PE = Gpio<GPIOE_BASE, Pin>;

template <uint8_t Pin>
using PF = Gpio<GPIOF_BASE, Pin>;

template <uint8_t Pin>
using PG = Gpio<GPIOG_BASE, Pin>;

}


#endif
